//this is a main program for testing the Geffe generator

#include <iostream>
#include "geffe.h"
#include <vector>
#include <random>
#include <ctime>

//some variables
std::vector<word32> primPoly(32); //primitive polynomials, grade <=32
std::default_random_engine randGen;

void initPrimPoly(){
  //store primitive polynomials in primPoly
  //the polynomials are from
  //http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf
  primPoly[0]=word32(1);
  primPoly[1]=word32(1);
  primPoly[2]=word32(3);
  primPoly[3]=word32(3);
  primPoly[4]=word32(3);
  primPoly[5]=word32(5);
  primPoly[6]=word32(3);
  primPoly[7]=word32(3);
  primPoly[8]=word32(135);
  primPoly[9]=word32(17);
  primPoly[10]=word32(9);
  primPoly[11]=word32(5);
  primPoly[12]=word32(1031);
  primPoly[13]=word32(297);
  primPoly[14]=word32(6147);
  primPoly[15]=word32(3);
  primPoly[16]=word32(37889);
  primPoly[17]=word32(9);
  primPoly[18]=word32(129);
  primPoly[19]=word32(1545);
  primPoly[20]=word32(9);
  primPoly[21]=word32(5);
  primPoly[22]=word32(3);
  primPoly[23]=word32(33);
  primPoly[24]=word32(2085);
  primPoly[25]=word32(9);
  primPoly[26]=word32(8429569);
  primPoly[27]=word32(12713985);
  primPoly[28]=word32(9);
  primPoly[29]=word32(5);
  primPoly[30]=word32(134219265);
  primPoly[31]=word32(9);
}

unsigned long createKey(int size){
  //creates a key with size bits
  //uniform distributed never all-zero
  unsigned long  maxKey=(1uL<<size)-1;
  std::uniform_int_distribution<unsigned long> distribution(1,maxKey);
  return distribution(randGen);
}

BitVector32 genKeystream(Geffe theGen,unsigned long length){
  //generates length bit of keystream
  BitVector32 keyStream;
  unsigned long nbrOfword32=(length>>5);
  unsigned long nbrOfBits=length&0x1f;
  for(unsigned long i=0;i<nbrOfword32;i++)
    keyStream.appendWord32(theGen.generateKeyStream32());
  for(unsigned long i=0;i<nbrOfBits;i++)
    keyStream.appendBit(theGen.generateKeyStream());
  return keyStream;
} 


int main(int argc,char* argv[]){
  std::cout << "Brute force of the Geffe generator\n";
  if(argc!=3){
    std::cout<<"Call with geffeBrute keyLength nbrOfiterations\n";
    exit(0);
  }
  int L=std::stoi(std::string(argv[1])); //read the keysize from argument
  int iterations=std::stoi(std::string(argv[2])); //Number of iterations
  int keyCount=0;
  initPrimPoly();
  //create a Geffe generator
  //int L=16; //total number of bits in the key
  int L1=L/3;
  int L2=L1+2;
  int L3=L-(L1+L2);
  std::cout << "Total key length: "<< L << std::endl;
  std::cout << "length 1: "<< L1 << std::endl;
  std::cout << "length 2: "<< L2 << std::endl;
  std::cout << "length 3: "<< L3 << std::endl;
  //use the calculated keylength to create a Geffe generator
  Geffe myGen(L1,L2,L3);
  myGen.initPoly(1,primPoly[L1]);
  myGen.initPoly(2,primPoly[L2]);
  myGen.initPoly(3,primPoly[L3]);
  //repeate the brute force iterations time
  clock_t startTime=clock();
  for(int i=0;i<iterations;i++){
    //generate key
    unsigned long k1=createKey(L1);
    unsigned long k2=createKey(L2);
    unsigned long k3=createKey(L3);
    //std::cout << "k1: " << k1 << std::endl;
    //std::cout << "k2: " << k2 << std::endl;
    //std::cout << "k3: " << k3 << std::endl;
    myGen.initKey(1,k1);
    myGen.initKey(2,k2);
    myGen.initKey(3,k3);
    //run the Geffe generator and produce 128 bit of keystream
    BitVector32 keystream;
    for(int i=0;i<4;i++)
      keystream.appendWord32(myGen.generateKeyStream32());
    //std::cout << keystream.toHexStringMSB()<< std::endl;
    //std::cout << keystream.weight() << std::endl;
    //do a brute force attack
    unsigned long max1=1uL<<L1;
    unsigned long max2=1uL<<L2;
    unsigned long max3=1uL<<L3;
    bool keyFound=false;
    unsigned long K1=1;
    unsigned long K2=1;
    unsigned long K3=1;
    while((!keyFound)&&(K3!=max3)){
	//generate a keystream from the guessed key
	myGen.initKey(1,K1);
	myGen.initKey(2,K2);
	myGen.initKey(3,K3);
	BitVector32 guessedStream;
	for(int i=0;i<4;i++)
	  guessedStream.appendWord32(myGen.generateKeyStream32());
	//xor with keystream
	guessedStream.xorOp(keystream);
	//check weight
	unsigned long w=guessedStream.weight();
	if(w==0){
	  //std::cout << "K1: " << K1 << std::endl;
	  //std::cout << "K2: " << K2 << std::endl;
	  //std::cout << "K3: " << K3 << std::endl;
	  if((K1==k1) && (K2==k2) && (K3==k3)){
	    keyFound=true;
	    keyCount++;
	    break;
	  }
	}
	//increase the key
	K1++;
	if(K1==max1){
	  K2++;
	  K1=1;
	  if(K2==max2){
	    K2=1;
	    K3++;
	  }
	}
    }
  }
  clock_t endTime=clock();
  double execTime=double(endTime-startTime)/CLOCKS_PER_SEC;
  std::cout << "Finished\n";
  std::cout << "Execution time: " << execTime << std::endl;
  double dTime=execTime/iterations;
  std::cout << "Execution time per iteration: " << dTime << std::endl;
  std::cout << "Number of iterations: " << iterations << std::endl;
  std::cout << "Key found: " << keyCount << std::endl;
 
}
